package com.spring;


import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Arrays;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: Jie Wang
 * @date: 2021/4/25 15:13
 * Spring 容器类
 */
public class MyApplicationContext {

    private Class configClass;
    //单例池
    private ConcurrentHashMap<String,Object> singletonMap = new ConcurrentHashMap<>();
    //Bean池
    private ConcurrentHashMap<String,BeanDefinition> beanMap = new ConcurrentHashMap<>();

    public MyApplicationContext(Class configClass) throws IOException {
        this.configClass = configClass;
        ComponentScan componentScan = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);
        String path = componentScan.value();
        //通过类加载器加载目标path下的类
        diguiSource(path);
    }

    private void diguiSource(String path) {
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        String replace = path.replace(".", "/");
        //通过类加载器获取资源路径下得资源
        URL resource = classLoader.getResource(replace);
        //获取资源目录
        File file = new File(resource.getFile());
        if(file.isDirectory()){
            //获取资源目录下的文件以及文件夹
            File[] files = file.listFiles();
            for (File f : files) {
                if(f.isDirectory()){
                    //如果是文件夹 需要继续递归
                    path = path + "."  + f.getName();
                    diguiSource(path);
                }else{
                    //获取带path的文件路径 eg： filename=com.wj.service.OrderService
                    String filename = path + "." + f.getName().substring(0,f.getName().indexOf(".class"));
                    System.out.println("filename=" + filename);
                    try {
                        //通过类加载器获取该类
                        Class<?> clazz = classLoader.loadClass(filename);
                        //如果被@Component注解标记就放入bean池中
                        if(clazz.isAnnotationPresent(Component.class)){
                            Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                            //获取@Component注解定义的bean名称
                            String beanName = componentAnnotation.value();
                            //生成一个“类定义”实例
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setClazz(clazz);
                            if(clazz.isAnnotationPresent(Scope.class)){
                                //原型bean用的时候再创建
                                Scope scopeAnnotation = clazz.getDeclaredAnnotation(Scope.class);
                                beanDefinition.setScope(scopeAnnotation.value());
                            }else{
                                //单例bean 先初始化一份 放到 singletonMap中
                                beanDefinition.setScope("singleton");
                                Object bean = createBean(beanDefinition);
                                singletonMap.put(beanName,bean);
                            }
                            beanMap.put(beanName,beanDefinition);
                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                }
            }
        }
    }

    public Object getBean(String beanName){
        if(beanMap.containsKey(beanName)){
            BeanDefinition beanDefinition = beanMap.get(beanName);
            if(beanDefinition.getScope().equals("singleton")){
                Object o = singletonMap.get(beanName);
                return o;
            }else{
                Object bean = createBean(beanDefinition);
                return bean;
            }
        }else{
            throw new NullPointerException("不存在对应的bean");
        }
    }

    private Object createBean(BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getClazz();
        try {
            Object instance = clazz.getDeclaredConstructor().newInstance();
            for (Field declaredField : clazz.getDeclaredFields()) {
                if(declaredField.isAnnotationPresent(Autowired.class)){
                    Object bean = getBean(declaredField.getName());
                    declaredField.setAccessible(true);
                    declaredField.set(instance, bean);
                }
            }
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }


}
